Tiffany Chan

Computer Vision Project Using Convolutional Neural Network Model

Plant Seedlings Classification

1. Import the libraries, load dataset, print shape of data, visualize the images in dataset. (5 Marks).

Import the libraries

In [ ]:
#Importing the necessary libraries for this project. There are others that are imported later on, as needed.
import cv2
import math
import numpy as np
import pandas as pd
from glob import glob
from matplotlib import pyplot as plt

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D, GlobalMaxPooling2D
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.optimizers import RMSprop, Adam
from keras.utils.np_utils import to_categorical # convert to one-hot-encoding
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from sklearn.preprocessing import LabelBinarizer
from tensorflow.keras import datasets, models, layers, optimizers
from tensorflow.keras import regularizers
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping     
from sklearn.metrics import classification_report, confusion_matrix   

Load data

Following the code that was provided to attain the plant seedling photos and labels provided by Kaggle:

In [ ]:
train_path = "/content/drive/MyDrive/Colab Notebooks/plant_seedlings/train.zip"
In [ ]:
!mkdir temp_train
In [ ]:
# Extract the files from dataset to temp_train and temp_test folders (Dataset is a zip file.)
from zipfile import ZipFile
with ZipFile(train_path, 'r') as zip:
  zip.extractall('./temp_train')
In [ ]:
path = "./temp_train/*/*.png"                              # The path to all images in training set. (* means include all folders and files.)
files = glob(path)

trainImg = []                                              # Initialize empty list to store the image data as numbers.
trainLabel = []                                            # Initialize empty list to store the labels of images
j = 1
num = len(files)

# Obtain images and resizing, obtain labels
for img in files:
    '''
    Append the image data to trainImg list.
    Append the labels to trainLabel list.
    '''
    print(str(j) + "/" + str(num), end="\r")
    trainImg.append(cv2.resize(cv2.imread(img), (128, 128)))  # Get image (with resizing to 128x128)
    trainLabel.append(img.split('/')[-2])  # Get image label (folder name contains the class to which the image belong)
    j += 1

trainImg = np.asarray(trainImg)  # Train images set
trainLabel = pd.DataFrame(trainLabel)  # Train labels set

Print shape of data

In [ ]:
#Shape of data
print(trainImg.shape)
print(trainLabel.shape)
(4750, 128, 128, 3)
(4750, 1)

We can see from the shape of the data that there are in total 4750 photos of plant seedlings. Each photo is 128x128 pixels in terms of dimensions. 3 means that these photos are in color because there are 3 RBG channels.

In [ ]:
#The frequency values of the different plant seedlings.
trainLabel.value_counts()
Out[ ]:
Loose Silky-bent             654
Common Chickweed             611
Scentless Mayweed            516
Small-flowered Cranesbill    496
Fat Hen                      475
Charlock                     390
Sugar beet                   385
Cleavers                     287
Black-grass                  263
Shepherds Purse              231
Maize                        221
Common wheat                 221
dtype: int64
In [ ]:
#The way the trainlabel is distributed. 
#Because there are consecutive photos of the same seedling that follow each other, we will need to shuffle them in order to reduce any sort of bias. 
#This can be done by default in train-test-split, and can be declared in "shuffle" in the actual running of the model later.

trainLabel.head()
Out[ ]:
0
0 Small-flowered Cranesbill
1 Small-flowered Cranesbill
2 Small-flowered Cranesbill
3 Small-flowered Cranesbill
4 Small-flowered Cranesbill

Visualize the images in the dataset

In [ ]:
#Let's look at some pictures to make sure they got imported correctly.
i = 0
image = trainImg[i]
label = trainLabel[i][0]    # Let us not worry about the labels for now because the labels haven't been transformed into an array yet.
#print(' Label \n Label Id: {} \n Name: {}'.format(label, label_dict[label]))
print(label)                # Let us not worry about the labels for now because the labels haven't been transformed into an array yet.
plt.imshow(image);
Small-flowered Cranesbill
In [ ]:
#Let's look at #122. (This is random to see if we can generate other pictures.)
i = 122
image = trainImg[i]
plt.imshow(image);

These photos are beautiful! However, we will need to ultimately transform these photos into pixel values that range from 0 to 255 for the computer to understand them.

2. Data Pre-processing: (15 Marks): a. Normalization, b. Gaussian Blurring, c. Visualize data after pre-processing.

2A.

In [ ]:
#a. Normalization:

# Normalizing the data is important for neural networks. We need to change the pixel values to float values so that we could get decimal values after division.
X = trainImg.astype('float32') / 255.0                       # 255 is the maximum value for all colored pixels. 255 equates to a white pixel, while 0 equates to a black pixel. All other colors and variations fall in between. 
In [ ]:
#Let's check to see if normalization worked.
X
Out[ ]:
array([[[[0.14901961, 0.21960784, 0.3137255 ],
         [0.13725491, 0.1882353 , 0.26666668],
         [0.1882353 , 0.21568628, 0.28235295],
         ...,
         [0.27450982, 0.37254903, 0.45882353],
         [0.23529412, 0.32941177, 0.41568628],
         [0.2627451 , 0.33333334, 0.4117647 ]],

        [[0.1882353 , 0.2627451 , 0.3372549 ],
         [0.25490198, 0.3137255 , 0.36862746],
         [0.17254902, 0.20784314, 0.29803923],
         ...,
         [0.25490198, 0.34117648, 0.42745098],
         [0.20392157, 0.2627451 , 0.36078432],
         [0.26666668, 0.3372549 , 0.41568628]],

        [[0.21568628, 0.2901961 , 0.35686275],
         [0.27450982, 0.3529412 , 0.42352942],
         [0.3019608 , 0.37254903, 0.4509804 ],
         ...,
         [0.10980392, 0.14901961, 0.25490198],
         [0.2       , 0.25882354, 0.3529412 ],
         [0.27450982, 0.3529412 , 0.43137255]],

        ...,

        [[0.23921569, 0.30980393, 0.39607844],
         [0.24313726, 0.3254902 , 0.39607844],
         [0.20392157, 0.2784314 , 0.36078432],
         ...,
         [0.13725491, 0.1764706 , 0.24313726],
         [0.14509805, 0.1882353 , 0.27058825],
         [0.11372549, 0.16470589, 0.24705882]],

        [[0.4862745 , 0.54509807, 0.57254905],
         [0.30980393, 0.36078432, 0.43137255],
         [0.20392157, 0.26666668, 0.35686275],
         ...,
         [0.19215687, 0.27450982, 0.3254902 ],
         [0.21960784, 0.29803923, 0.3529412 ],
         [0.19607843, 0.2784314 , 0.32941177]],

        [[0.49803922, 0.5686275 , 0.5921569 ],
         [0.3882353 , 0.44705883, 0.5058824 ],
         [0.16862746, 0.22745098, 0.30980393],
         ...,
         [0.2784314 , 0.34509805, 0.3882353 ],
         [0.27450982, 0.3254902 , 0.3764706 ],
         [0.26666668, 0.34117648, 0.38431373]]],


       [[[0.23529412, 0.3019608 , 0.3764706 ],
         [0.21568628, 0.2784314 , 0.3647059 ],
         [0.21960784, 0.2901961 , 0.3764706 ],
         ...,
         [0.21176471, 0.28235295, 0.34901962],
         [0.19215687, 0.26666668, 0.33333334],
         [0.25882354, 0.31764707, 0.3882353 ]],

        [[0.20784314, 0.26666668, 0.3529412 ],
         [0.19215687, 0.25490198, 0.34509805],
         [0.23137255, 0.29411766, 0.38039216],
         ...,
         [0.20784314, 0.2784314 , 0.34117648],
         [0.1882353 , 0.25882354, 0.32941177],
         [0.21568628, 0.2784314 , 0.34509805]],

        [[0.20784314, 0.27058825, 0.3529412 ],
         [0.22352941, 0.28627452, 0.3647059 ],
         [0.23921569, 0.3019608 , 0.38039216],
         ...,
         [0.19215687, 0.27058825, 0.33333334],
         [0.20392157, 0.27450982, 0.34509805],
         [0.21176471, 0.2784314 , 0.34901962]],

        ...,

        [[0.16862746, 0.22745098, 0.2784314 ],
         [0.18431373, 0.2509804 , 0.29803923],
         [0.20392157, 0.27450982, 0.32156864],
         ...,
         [0.33333334, 0.39607844, 0.42745098],
         [0.35686275, 0.41960785, 0.44313726],
         [0.35686275, 0.41568628, 0.43529412]],

        [[0.14509805, 0.20392157, 0.25490198],
         [0.18431373, 0.2509804 , 0.29803923],
         [0.20784314, 0.27450982, 0.3254902 ],
         ...,
         [0.29411766, 0.3647059 , 0.40392157],
         [0.29803923, 0.36862746, 0.4       ],
         [0.29803923, 0.36078432, 0.39215687]],

        [[0.14509805, 0.20392157, 0.25882354],
         [0.1882353 , 0.2509804 , 0.3019608 ],
         [0.20784314, 0.27450982, 0.3254902 ],
         ...,
         [0.2627451 , 0.33333334, 0.38431373],
         [0.27058825, 0.3372549 , 0.38431373],
         [0.25882354, 0.32156864, 0.36862746]]],


       [[[0.2509804 , 0.31764707, 0.37254903],
         [0.22352941, 0.28235295, 0.34117648],
         [0.15294118, 0.20392157, 0.2784314 ],
         ...,
         [0.25490198, 0.29803923, 0.36862746],
         [0.25882354, 0.30588236, 0.36862746],
         [0.28627452, 0.32941177, 0.3882353 ]],

        [[0.21568628, 0.27450982, 0.34117648],
         [0.21568628, 0.27450982, 0.33333334],
         [0.22745098, 0.28235295, 0.34117648],
         ...,
         [0.26666668, 0.31764707, 0.38431373],
         [0.30588236, 0.35686275, 0.4117647 ],
         [0.2784314 , 0.33333334, 0.38431373]],

        [[0.25490198, 0.3019608 , 0.36078432],
         [0.27058825, 0.32156864, 0.37254903],
         [0.32156864, 0.38039216, 0.4117647 ],
         ...,
         [0.2784314 , 0.34117648, 0.39215687],
         [0.3019608 , 0.36078432, 0.4117647 ],
         [0.3019608 , 0.3647059 , 0.4117647 ]],

        ...,

        [[0.31764707, 0.36078432, 0.4       ],
         [0.28627452, 0.33333334, 0.35686275],
         [0.27058825, 0.3137255 , 0.33333334],
         ...,
         [0.16078432, 0.22352941, 0.32156864],
         [0.14901961, 0.20784314, 0.29411766],
         [0.25882354, 0.32156864, 0.35686275]],

        [[0.25490198, 0.3019608 , 0.34901962],
         [0.25882354, 0.29411766, 0.34117648],
         [0.23921569, 0.2784314 , 0.3254902 ],
         ...,
         [0.11764706, 0.19607843, 0.29411766],
         [0.12941177, 0.19215687, 0.28235295],
         [0.2509804 , 0.30588236, 0.3529412 ]],

        [[0.27450982, 0.31764707, 0.36078432],
         [0.21960784, 0.2627451 , 0.32941177],
         [0.18039216, 0.23921569, 0.3137255 ],
         ...,
         [0.10196079, 0.17254902, 0.27450982],
         [0.12156863, 0.18431373, 0.27450982],
         [0.2509804 , 0.3019608 , 0.3529412 ]]],


       ...,


       [[[0.25882354, 0.33333334, 0.39215687],
         [0.20392157, 0.2627451 , 0.34117648],
         [0.15294118, 0.22352941, 0.32156864],
         ...,
         [0.19607843, 0.1764706 , 0.2       ],
         [0.18431373, 0.16470589, 0.1764706 ],
         [0.2627451 , 0.28627452, 0.3019608 ]],

        [[0.2509804 , 0.31764707, 0.38431373],
         [0.22745098, 0.2784314 , 0.34901962],
         [0.14901961, 0.20784314, 0.28627452],
         ...,
         [0.21176471, 0.20784314, 0.21960784],
         [0.20392157, 0.1882353 , 0.21176471],
         [0.27058825, 0.2901961 , 0.32156864]],

        [[0.21960784, 0.28235295, 0.3647059 ],
         [0.25882354, 0.31764707, 0.38431373],
         [0.23137255, 0.3019608 , 0.37254903],
         ...,
         [0.46666667, 0.4509804 , 0.44313726],
         [0.45882353, 0.43529412, 0.44313726],
         [0.3647059 , 0.37254903, 0.39607844]],

        ...,

        [[0.24705882, 0.39607844, 0.5019608 ],
         [0.2       , 0.36862746, 0.49019608],
         [0.16470589, 0.31764707, 0.44313726],
         ...,
         [0.76862746, 0.7411765 , 0.7176471 ],
         [0.75686276, 0.7372549 , 0.72156864],
         [0.75686276, 0.73333335, 0.7176471 ]],

        [[0.23529412, 0.4       , 0.5254902 ],
         [0.22745098, 0.40784314, 0.54509807],
         [0.14117648, 0.3019608 , 0.4392157 ],
         ...,
         [0.7647059 , 0.7411765 , 0.7176471 ],
         [0.7529412 , 0.7294118 , 0.7137255 ],
         [0.75686276, 0.73333335, 0.7137255 ]],

        [[0.1882353 , 0.3529412 , 0.5019608 ],
         [0.16078432, 0.34509805, 0.49411765],
         [0.14509805, 0.30588236, 0.45490196],
         ...,
         [0.7529412 , 0.73333335, 0.70980394],
         [0.7607843 , 0.7372549 , 0.72156864],
         [0.7607843 , 0.7294118 , 0.70980394]]],


       [[[0.29803923, 0.35686275, 0.42352942],
         [0.28627452, 0.34509805, 0.41568628],
         [0.24705882, 0.3137255 , 0.38431373],
         ...,
         [0.15294118, 0.22745098, 0.29803923],
         [0.12156863, 0.19607843, 0.27058825],
         [0.09803922, 0.1764706 , 0.25882354]],

        [[0.28235295, 0.34509805, 0.41568628],
         [0.28627452, 0.34901962, 0.41960785],
         [0.2509804 , 0.32156864, 0.39215687],
         ...,
         [0.18039216, 0.24705882, 0.3254902 ],
         [0.15294118, 0.21176471, 0.28627452],
         [0.10980392, 0.1764706 , 0.25490198]],

        [[0.27450982, 0.34509805, 0.40784314],
         [0.25490198, 0.33333334, 0.39607844],
         [0.23921569, 0.31764707, 0.38431373],
         ...,
         [0.16862746, 0.23137255, 0.30980393],
         [0.11764706, 0.18039216, 0.25882354],
         [0.05490196, 0.12941177, 0.20392157]],

        ...,

        [[0.07058824, 0.18431373, 0.31764707],
         [0.07450981, 0.20392157, 0.33333334],
         [0.08235294, 0.23137255, 0.3529412 ],
         ...,
         [0.15294118, 0.20784314, 0.27450982],
         [0.1882353 , 0.23921569, 0.30980393],
         [0.24705882, 0.3019608 , 0.3647059 ]],

        [[0.07058824, 0.1882353 , 0.31764707],
         [0.06666667, 0.19607843, 0.32941177],
         [0.08235294, 0.22352941, 0.34901962],
         ...,
         [0.15294118, 0.1882353 , 0.2509804 ],
         [0.14117648, 0.1764706 , 0.25490198],
         [0.23137255, 0.27058825, 0.34509805]],

        [[0.08235294, 0.2       , 0.32156864],
         [0.07450981, 0.19607843, 0.32941177],
         [0.09411765, 0.21960784, 0.34901962],
         ...,
         [0.13725491, 0.18039216, 0.24705882],
         [0.15294118, 0.2       , 0.2784314 ],
         [0.1764706 , 0.22745098, 0.3137255 ]]],


       [[[0.10980392, 0.16078432, 0.21176471],
         [0.13333334, 0.19607843, 0.24705882],
         [0.13333334, 0.18039216, 0.23921569],
         ...,
         [0.15294118, 0.16862746, 0.22352941],
         [0.14509805, 0.1882353 , 0.25490198],
         [0.25882354, 0.30980393, 0.36862746]],

        [[0.09019608, 0.14509805, 0.23921569],
         [0.09803922, 0.17254902, 0.25490198],
         [0.10588235, 0.1764706 , 0.25490198],
         ...,
         [0.18431373, 0.21568628, 0.27058825],
         [0.15294118, 0.19215687, 0.27058825],
         [0.18431373, 0.27450982, 0.37254903]],

        [[0.17254902, 0.24705882, 0.3529412 ],
         [0.19607843, 0.27058825, 0.36078432],
         [0.21960784, 0.29803923, 0.38431373],
         ...,
         [0.14117648, 0.1764706 , 0.2627451 ],
         [0.13725491, 0.20784314, 0.3137255 ],
         [0.15686275, 0.35686275, 0.5137255 ]],

        ...,

        [[0.15686275, 0.14509805, 0.15686275],
         [0.15686275, 0.14509805, 0.16862746],
         [0.20392157, 0.1764706 , 0.19215687],
         ...,
         [0.10980392, 0.11764706, 0.11372549],
         [0.21960784, 0.21568628, 0.22745098],
         [0.24705882, 0.23921569, 0.2509804 ]],

        [[0.14117648, 0.12941177, 0.14509805],
         [0.14901961, 0.14117648, 0.15686275],
         [0.12941177, 0.10196079, 0.14117648],
         ...,
         [0.18039216, 0.1764706 , 0.19607843],
         [0.23137255, 0.21960784, 0.24313726],
         [0.15686275, 0.12156863, 0.15294118]],

        [[0.16862746, 0.15294118, 0.17254902],
         [0.13333334, 0.1254902 , 0.14509805],
         [0.14901961, 0.14509805, 0.17254902],
         ...,
         [0.16470589, 0.16470589, 0.18039216],
         [0.1764706 , 0.16862746, 0.18039216],
         [0.21568628, 0.20784314, 0.21176471]]]], dtype=float32)

Yes, normalization worked. Values are floats. Values range between 0 and 1.

2B.

In [ ]:
# b. Gaussian Blurring
from scipy.ndimage.filters import gaussian_filter

blurred = gaussian_filter(X, sigma=0.8)

Gaussian filter is a way to achieve Gaussian blurring. I liked this function because there is a sigma value that could be calibrated to attain better model performance. I tweaked this value many times and found 0.8 to give the better results.

2C.

In [ ]:
#c. Visualize data after pre-processing.

#Let's see how #122 looks compared to the original photo above.
i = 122
image_blur122 = blurred[i]
plt.imshow(image_blur122);

Gaussian blurring was a success!

We could have also converted the images to grayscale, but it wasn't really necessary. It would have perhaps quickened the wait time for the running of the model.

3. Make data compatible: (10 Marks): a. Split the dataset into training,testing, and validation set. (Hint: First split train images and train labels into training and testing set with test_size = 0.3. Then further split test data into test and validation set with test_size = 0.5) b. Reshape data into shapes compatible with Keras models. c. Convert labels from digits to one hot vectors. d. Print the label for y_train[0].

3A.

In [ ]:
#Splitting dataset into training, testing and validation sets.
from sklearn.model_selection import train_test_split                            #Also listed in the first cell, but I re-wrote this for convenience.

#Splitting data into training (70%) and valtest (30%) datasets. 
x_train, x_valtest, y_train, y_valtest = train_test_split(blurred,trainLabel , test_size=0.30, random_state=1)

#Making the validation set 50%, test set 50% of the  valtest dataframe (25% of the remaining data from the last split)
x_val, x_test, y_val, y_test = train_test_split(x_valtest, y_valtest, test_size=0.5, random_state=1)
In [ ]:
#Checking to see if the splitting distributions are correct and we need this information for Keras compatibility reshaping.
print(x_train.shape)
print(x_val.shape)
print(x_test.shape)
(3325, 128, 128, 3)
(712, 128, 128, 3)
(713, 128, 128, 3)

3B.

In [ ]:
#Reshaping data into shapes compatible with Keras models.

X_train = x_train.reshape(x_train.shape[0], 128, 128, 3)
X_val = x_val.reshape(x_val.shape[0], 128, 128, 3)
X_test = x_test.reshape(x_test.shape[0], 128, 128, 3)
In [ ]:
# Convert labels from digits to one hot vectors. 

#The target variable (y_train, y_val, and y_train) are still in strings because I haven't done anything to them yet.
#Since the question wants to transform them from digits to one hot vectors, we must transform the strings to categorical numbers first, and then apply one-hot-encoding.
#Get dummies transforms strings to one hot vectors directly.
#One-hot-encoding can only transform numerical values (or digits) into one-hot-vectors

#Let's look at the state of our target variable first before we continue pre-processing
y_train
Out[ ]:
0
3702 Common Chickweed
4252 Maize
945 Loose Silky-bent
3610 Fat Hen
4046 Common Chickweed
... ...
2895 Charlock
2763 Scentless Mayweed
905 Loose Silky-bent
3980 Common Chickweed
235 Small-flowered Cranesbill

3325 rows × 1 columns

In [ ]:
#Let's rename the target variable as 'seed' instead of '0' to avoid confusion with the digital categorization of each seed.
y_train.columns = ['seed']
print(y_train)               #Checking to see that we changed the name of the variable to avoid confusion

#Repeat for y_val and y_test
y_val.columns = ['seed']
y_test.columns = ['seed']
                           seed
3702           Common Chickweed
4252                      Maize
945            Loose Silky-bent
3610                    Fat Hen
4046           Common Chickweed
...                         ...
2895                   Charlock
2763          Scentless Mayweed
905            Loose Silky-bent
3980           Common Chickweed
235   Small-flowered Cranesbill

[3325 rows x 1 columns]

3C.

In [ ]:
# c. Convert labels from digits to one hot vectors.

#Encode the string categories into numerical categories because the question asks to convert digits to one hot vectors, not strings to one hot vectors. 
#The seedling digits will be assign alphabetically.
#Black-grass will be 0, and sugar beet will be 11.

#Variable is in column form and must be in a horizontal, array form.
y_train = np.ravel(y_train)

#Set label encoder
from sklearn import preprocessing                                               #Re-wrote this for convenience
le = preprocessing.LabelEncoder()
le.fit(y_train)
list(le.classes_)  #Looking at the different classes and their assigned order
Out[ ]:
['Black-grass',
 'Charlock',
 'Cleavers',
 'Common Chickweed',
 'Common wheat',
 'Fat Hen',
 'Loose Silky-bent',
 'Maize',
 'Scentless Mayweed',
 'Shepherds Purse',
 'Small-flowered Cranesbill',
 'Sugar beet']
In [ ]:
#Transform y_train into digits. Also check to see it was done properly.
y_train = le.transform(y_train)
y_train
Out[ ]:
array([ 3,  7,  6, ...,  6,  3, 10])
In [ ]:
#Repeat for y_val and y_test
#Reuse the same labelencoder to get the same order of the categories

y_val = le.transform(y_val)
y_test = le.transform(y_test)
/usr/local/lib/python3.7/dist-packages/sklearn/preprocessing/_label.py:268: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
  y = column_or_1d(y, warn=True)
In [ ]:
#Employ labelbinarizer to transform single variable digit categories into features.


from sklearn.preprocessing import LabelBinarizer                          #Re-wrote this for convenience
enc = LabelBinarizer()
y_train = enc.fit_transform(y_train)
y_val = enc.fit_transform(y_val)
y_test = enc.fit_transform(y_test)

3D.

In [ ]:
#d. Print out y_train[0]
y_train[0]
Out[ ]:
array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0])

This shows that the first case is the seedling located in the 4th position on this list, which is common chickweed.

In [ ]:
#Just checking the dtype of y_train. Although it is categorical in theory, the labels are still interger in nature.
y_train.dtype
Out[ ]:
dtype('int64')

4. Building CNN (15 Marks): a. Define layers. b. Set optimizer and loss function. (Use Adam optimizer and categorical crossentropy.)

The biggest challenge was dealing with overfit and underfit models. Here, I discuss what I did to combat these issues.

Tuning the following hyperparameters and associated items:

Limiting the number of convolutional layers: I initially had 4 convolutional layers in my CNN model. Everytime I ran the model with excessive layers, the model would ultimately reach really high training performance but would bomb on the validation data. So, I opted for a less complicated model by shedding down the layers and keeping only 2 convolutional layers with accompanying pooling layers, followed by a simple deep neural network with not too many neurons.

Adding Dropout to each section of the CNN model: Dropout randomly drops out neurons in the deep neural network after the convolutional layers. Dropout can also follow convolutional layers in order to minimize the weights the input is exposed to when running the model. I found this very helpful with layers that had excessive parameters (weights), especially the dense layer in the deep neural network. I also discovered that when you set the dropout to 0.5, it can limit the model's potential and the accuracy for the training data ends up being around 50% during each epoch. Setting too many Dropouts to 0.5 could lead to the model being underfit, and the model doesn't learn enough to make decent predictions. I learned to tackle layers with excessive parameters with higher dropouts will handle overfitting better, and lower dropouts to layers with manageable weights.

Penalizing the parameters (weights) by using a regularizer like L1 in the dense layer: After looking at the model summary, I realized the dense layer in the neural network had excessive parameters. I wanted to find a way in controlling the weights and found out regularizers penalize the weights. Using one regularizer on a layer with excessive weights would help minimize the overfitting issues.

Other: Averagepooling vs. maxpooling: In this scenario, maxpooling had higher accuracy results and lower loss.

Sigma: Changing the sigma value in Gaussian blurring seemed to be a good idea as well. I tried 0.6, 0.8 and 1. For some reason, 0.8 yielded better outcomes.

Increasing the number of epochs and and changing batch size also improved results. 35 epochs with 35 for batch size seemed to be ideal after many attempts.

ReLu vs. sigmoid: ReLu yielded faster results (1 minute vs. 4 minutes). It has a tendency to converge faster than sigmoid. With ReLu, we don't have to worry too much about a vanishing gradient.

Switching the number of filters and filter dimensions was also attempted. 32 filters that were 3x3 for each convolutional layer seemed to yield the better results.

In [ ]:
#Building the model.
#This is the best model I came up with.

from tensorflow.keras import datasets, models, layers, optimizers                       #Re-wrote this for convenience
from tensorflow.keras.layers import Conv2D                                              #Re-wrote this for convenience
from tensorflow.keras import regularizers                                               #Re-wrote this for convenience

model = Sequential()
model.add(Conv2D(filters=32, kernel_size=3, activation="relu", input_shape=(128, 128, 3)))
model.add(layers.BatchNormalization())                   #Normalizes the results from each layer before it precedes to the next layer
model.add(layers.MaxPooling2D((2, 2)))                   #Records the maximum value in the neighborhood after the filter passes the pixels.
model.add(layers.Dropout(0.2))                           #Cancels out 20% of the parameters

model.add(Conv2D(filters=32, kernel_size=3, activation="relu"))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.5))                           #Cancels out 50% of the parameters

model.add(Flatten())
model.add(Dense(20, activation="relu", kernel_regularizer=regularizers.l1(l=0.01)))           #Additional L1 regularizer to penalize the weights in the layer
model.add(Dropout(rate=0.1))                             #Cancels out 10% of the parameters
model.add(Dense(12, activation="softmax"))               #Use softmax activation function for multiclass classification 

model.summary()
Model: "sequential_6"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_12 (Conv2D)           (None, 126, 126, 32)      896       
_________________________________________________________________
batch_normalization_12 (Batc (None, 126, 126, 32)      128       
_________________________________________________________________
max_pooling2d_12 (MaxPooling (None, 63, 63, 32)        0         
_________________________________________________________________
dropout_14 (Dropout)         (None, 63, 63, 32)        0         
_________________________________________________________________
conv2d_13 (Conv2D)           (None, 61, 61, 32)        9248      
_________________________________________________________________
batch_normalization_13 (Batc (None, 61, 61, 32)        128       
_________________________________________________________________
max_pooling2d_13 (MaxPooling (None, 30, 30, 32)        0         
_________________________________________________________________
dropout_15 (Dropout)         (None, 30, 30, 32)        0         
_________________________________________________________________
flatten_6 (Flatten)          (None, 28800)             0         
_________________________________________________________________
dense_12 (Dense)             (None, 20)                576020    
_________________________________________________________________
dropout_16 (Dropout)         (None, 20)                0         
_________________________________________________________________
dense_13 (Dense)             (None, 12)                252       
=================================================================
Total params: 586,672
Trainable params: 586,544
Non-trainable params: 128
_________________________________________________________________
In [ ]:
#Select the hyperparameters for the optimizer
opt = optimizers.Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08)

# Compile the model. Use Adam and categorical crossentropy because it was in the question.
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
In [ ]:
# We set early stopping so that we don't lose time if the val_loss doesn't improve by 0.001 after 10 epochs.
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping                            #Re-wrote this for convenience

early_stopping = EarlyStopping(monitor='val_loss', min_delta=0.001, patience=10)

#Adding Model Checkpoint saves the weights whenever val_loss achieves a lower value. 

model_checkpoint =  ModelCheckpoint('cifar_cnn_checkpoint_{epoch:02d}_loss{val_loss:.4f}.h5',
                                                           monitor='val_loss',
                                                           verbose=1,
                                                           save_best_only=True,
                                                           save_weights_only=True,
                                                           mode='auto',
                                                           period=1)
WARNING:tensorflow:`period` argument is deprecated. Please use `save_freq` to specify the frequency in number of batches seen.
In [ ]:
#Training the model on the train data and validation sets.

history = model.fit(X_train,
                    y_train,
                    batch_size=35,                #35 for batch size was found to be ideal
                    epochs=35,                    #35 epochs was the best, with early stopping, of course.
                    validation_data=(X_val, y_val),
                    shuffle=True,                 #Shuffle, in order to limit the bias.
                    verbose=1,
                    callbacks=[early_stopping,model_checkpoint])

# Plot training history so that we can keep track of the minimal changes.
# Plotting train vs. validation loss.
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')      #This is actually on the validation set. Minor mistake on the label on my part.
plt.legend()
plt.show()
Epoch 1/35
95/95 [==============================] - 83s 862ms/step - loss: 23.1984 - accuracy: 0.1637 - val_loss: 10.7037 - val_accuracy: 0.1067

Epoch 00001: val_loss improved from inf to 10.70375, saving model to cifar_cnn_checkpoint_01_loss10.7037.h5
Epoch 2/35
95/95 [==============================] - 81s 852ms/step - loss: 8.7544 - accuracy: 0.4266 - val_loss: 9.4468 - val_accuracy: 0.0632

Epoch 00002: val_loss improved from 10.70375 to 9.44683, saving model to cifar_cnn_checkpoint_02_loss9.4468.h5
Epoch 3/35
95/95 [==============================] - 81s 853ms/step - loss: 7.6678 - accuracy: 0.5430 - val_loss: 9.1672 - val_accuracy: 0.1517

Epoch 00003: val_loss improved from 9.44683 to 9.16720, saving model to cifar_cnn_checkpoint_03_loss9.1672.h5
Epoch 4/35
95/95 [==============================] - 81s 850ms/step - loss: 7.4315 - accuracy: 0.5369 - val_loss: 10.3917 - val_accuracy: 0.0815

Epoch 00004: val_loss did not improve from 9.16720
Epoch 5/35
95/95 [==============================] - 81s 851ms/step - loss: 7.0554 - accuracy: 0.6307 - val_loss: 9.4639 - val_accuracy: 0.0829

Epoch 00005: val_loss did not improve from 9.16720
Epoch 6/35
95/95 [==============================] - 80s 847ms/step - loss: 6.7485 - accuracy: 0.6410 - val_loss: 8.2915 - val_accuracy: 0.2065

Epoch 00006: val_loss improved from 9.16720 to 8.29150, saving model to cifar_cnn_checkpoint_06_loss8.2915.h5
Epoch 7/35
95/95 [==============================] - 82s 860ms/step - loss: 6.6637 - accuracy: 0.6735 - val_loss: 7.0316 - val_accuracy: 0.5183

Epoch 00007: val_loss improved from 8.29150 to 7.03157, saving model to cifar_cnn_checkpoint_07_loss7.0316.h5
Epoch 8/35
95/95 [==============================] - 81s 853ms/step - loss: 6.4611 - accuracy: 0.6598 - val_loss: 6.5233 - val_accuracy: 0.7191

Epoch 00008: val_loss improved from 7.03157 to 6.52328, saving model to cifar_cnn_checkpoint_08_loss6.5233.h5
Epoch 9/35
95/95 [==============================] - 81s 850ms/step - loss: 6.2059 - accuracy: 0.6649 - val_loss: 13.2749 - val_accuracy: 0.1713

Epoch 00009: val_loss did not improve from 6.52328
Epoch 10/35
95/95 [==============================] - 80s 847ms/step - loss: 6.7154 - accuracy: 0.6615 - val_loss: 6.2610 - val_accuracy: 0.5983

Epoch 00010: val_loss improved from 6.52328 to 6.26095, saving model to cifar_cnn_checkpoint_10_loss6.2610.h5
Epoch 11/35
95/95 [==============================] - 80s 846ms/step - loss: 5.9747 - accuracy: 0.6899 - val_loss: 7.3171 - val_accuracy: 0.3919

Epoch 00011: val_loss did not improve from 6.26095
Epoch 12/35
95/95 [==============================] - 80s 847ms/step - loss: 6.2542 - accuracy: 0.6896 - val_loss: 9.0393 - val_accuracy: 0.4242

Epoch 00012: val_loss did not improve from 6.26095
Epoch 13/35
95/95 [==============================] - 80s 846ms/step - loss: 6.0793 - accuracy: 0.6830 - val_loss: 6.2566 - val_accuracy: 0.4705

Epoch 00013: val_loss improved from 6.26095 to 6.25665, saving model to cifar_cnn_checkpoint_13_loss6.2566.h5
Epoch 14/35
95/95 [==============================] - 80s 843ms/step - loss: 5.8804 - accuracy: 0.6919 - val_loss: 4.8895 - val_accuracy: 0.8006

Epoch 00014: val_loss improved from 6.25665 to 4.88950, saving model to cifar_cnn_checkpoint_14_loss4.8895.h5
Epoch 15/35
95/95 [==============================] - 81s 858ms/step - loss: 5.3739 - accuracy: 0.6999 - val_loss: 7.7489 - val_accuracy: 0.3862

Epoch 00015: val_loss did not improve from 4.88950
Epoch 16/35
95/95 [==============================] - 80s 844ms/step - loss: 5.6260 - accuracy: 0.7285 - val_loss: 5.5319 - val_accuracy: 0.7261

Epoch 00016: val_loss did not improve from 4.88950
Epoch 17/35
95/95 [==============================] - 80s 842ms/step - loss: 5.7588 - accuracy: 0.6891 - val_loss: 6.5975 - val_accuracy: 0.6334

Epoch 00017: val_loss did not improve from 4.88950
Epoch 18/35
95/95 [==============================] - 80s 841ms/step - loss: 5.6848 - accuracy: 0.7119 - val_loss: 6.3042 - val_accuracy: 0.5506

Epoch 00018: val_loss did not improve from 4.88950
Epoch 19/35
95/95 [==============================] - 80s 841ms/step - loss: 5.5129 - accuracy: 0.7424 - val_loss: 6.3134 - val_accuracy: 0.5702

Epoch 00019: val_loss did not improve from 4.88950
Epoch 20/35
95/95 [==============================] - 80s 842ms/step - loss: 5.5999 - accuracy: 0.7436 - val_loss: 6.9617 - val_accuracy: 0.3666

Epoch 00020: val_loss did not improve from 4.88950
Epoch 21/35
95/95 [==============================] - 80s 842ms/step - loss: 5.2494 - accuracy: 0.7312 - val_loss: 6.6915 - val_accuracy: 0.3736

Epoch 00021: val_loss did not improve from 4.88950
Epoch 22/35
95/95 [==============================] - 80s 843ms/step - loss: 5.2555 - accuracy: 0.7268 - val_loss: 7.8794 - val_accuracy: 0.4846

Epoch 00022: val_loss did not improve from 4.88950
Epoch 23/35
95/95 [==============================] - 80s 844ms/step - loss: 5.6010 - accuracy: 0.7295 - val_loss: 5.7386 - val_accuracy: 0.5899

Epoch 00023: val_loss did not improve from 4.88950
Epoch 24/35
95/95 [==============================] - 81s 850ms/step - loss: 5.1755 - accuracy: 0.7454 - val_loss: 5.4016 - val_accuracy: 0.7921

Epoch 00024: val_loss did not improve from 4.88950

Looking at the plotting of the train and validation loss, you can see that the validation loss, although not consistent in each epoch, the line (orange) still follows the overall downward trend of the train line (blue). This is good.

Also, the model stopped running after the 24/35 epoch, because the loss wasn't improving after 10 consecutive evaluations.

In [ ]:
#Train and validation accuracy line graph
plt.plot(history.history['accuracy'], label='Train')
plt.plot(history.history['val_accuracy'], label='Validation')
plt.legend()
plt.show()

The same goes for the accuracy line graph. Although the validation line started out very low and slow to learn, the general direction of the validation line (orange) is headed in the same direction as the training line (blue), despite the individual performances at each epoch.

In [ ]:
#Training Accuracy
scores = model.evaluate(X_train, y_train, verbose=1)
print('Train loss:', scores[0])
print('Train accuracy:', scores[1])
104/104 [==============================] - 18s 174ms/step - loss: 5.3276 - accuracy: 0.8030
Train loss: 5.327616214752197
Train accuracy: 0.8030075430870056
In [ ]:
#Validation Accuracy
scores = model.evaluate(X_val, y_val, verbose=1)
print('Validation loss:', scores[0])
print('Validation accuracy:', scores[1])
23/23 [==============================] - 4s 169ms/step - loss: 5.4016 - accuracy: 0.7921
Validation loss: 5.401580333709717
Validation accuracy: 0.7921348214149475
In [ ]:
#Test Accuracy
scores = model.evaluate(X_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])
23/23 [==============================] - 4s 170ms/step - loss: 5.3839 - accuracy: 0.7994
Test loss: 5.383861541748047
Test accuracy: 0.799439013004303

Results were good. Train accuracy: 80%, Validation accuracy: 79% and Test accuracy: 80%.

Overall, this CNN model is not overfit, and it was able to deliver a comparable performance on unseen data.

I ran many versions of the CNN model using the train and validate datasets to tune the hyperparameters. The biggest challenge was dealing with overfit and underfit models. I also ran the same model a second time and got slightly better results (Training accuracy: 88%, Validation accuracy: 86%, Test accuracy: 83%). Please see the extra part at the end if interested.

5. Fit and evaluate model and print confusion matrix. (10 Marks)

In [ ]:
#Let's get the predictions for all of X_test first.
predictions = model.predict(X_test)                           #This provides the probabilities of each image for each label.
rounded_predictions = np.argmax(predictions, axis = -1)       #This provides the prediction labels from 0-11.
In [ ]:
#We need to make sure that the predictions is in the right format. It has to look like the categorical array with 1s and 0s.
rounded_predictions             #This is each seedling's prediction in the test_data
Out[ ]:
array([ 1,  8,  7,  6,  6,  8,  6,  8,  6,  2,  7,  7,  3,  8, 10,  3,  8,
        8,  3,  6,  9,  3,  6,  3, 10,  5,  3,  9,  8,  3,  3, 10,  1, 10,
        6,  6,  3,  8, 10,  6,  5, 11,  6,  9,  6,  8,  5, 10,  3,  3,  3,
        1,  3,  6, 11,  9,  3,  7, 11,  6,  9,  8,  2,  8,  5,  9, 10, 11,
        8,  5,  3,  6,  1,  3, 11,  1, 10,  6, 11, 10, 10,  6,  3,  2,  0,
        6,  2,  1,  8,  7, 10, 11,  6,  5,  3,  3,  8,  8,  8,  3,  9,  3,
        8,  6,  1, 11, 10,  6,  8,  3,  5, 11,  6,  3,  6,  1,  4,  6,  3,
       10,  9,  1, 11,  5,  5,  6,  3,  6,  6, 11,  1,  3, 11, 10,  5,  5,
        5,  6,  9,  5,  6,  3,  3, 10,  6,  6, 11,  5,  7,  5,  6,  6,  1,
        8, 10, 11,  1,  8,  2,  8,  6, 11,  2,  5, 11, 10,  9,  3,  3, 10,
        6,  6,  1,  1, 10,  5, 10,  8, 11,  3,  8,  1,  1, 10,  1,  8,  4,
        2,  6,  8,  3,  1,  1, 10, 11,  6,  8,  3,  5, 10,  5,  3,  3,  3,
        2,  9,  8,  8,  6,  2,  6,  2,  1,  2,  1,  6, 10,  3,  6, 11,  3,
       11,  1, 10, 10,  6, 11,  8, 10, 11,  6,  5, 11,  8,  8, 10,  6,  8,
        2, 11, 11,  1,  6,  5, 11, 11, 10, 11,  1, 11,  3,  1,  6,  5,  2,
        8,  2,  5,  6,  2,  5,  6,  5,  6,  6,  8,  1,  5,  6,  6,  6,  5,
        1,  1, 11,  5, 11,  3,  8,  6,  5,  5, 11,  6,  6,  8,  8,  8,  5,
        8, 11,  3,  6,  9, 11,  6,  3,  6,  2,  6,  5,  6,  3,  6,  8,  6,
        2,  6,  3,  8,  2,  9,  1,  6, 10,  3,  5, 11,  8, 10,  3,  8,  2,
        3,  1,  5,  6,  9,  5,  8,  6,  9,  9,  5, 10,  3, 10, 10, 10,  1,
        3, 10,  8,  1,  4,  8,  3,  6,  6, 11, 11,  4,  9,  3,  1, 10,  6,
        1,  5,  4,  5,  6, 11,  6,  1, 10,  9,  2,  5,  3,  6,  5,  1,  1,
        9,  6,  8, 10, 11,  3,  6,  9,  3,  0, 10,  4,  3, 10,  8,  6, 10,
        9, 11,  3,  6,  5,  3, 11,  3,  6,  6,  8, 11,  8,  3, 10, 10, 11,
        1,  0,  6,  1,  5,  9, 10, 11,  1, 11,  8,  5, 10, 11,  9,  6,  7,
        5,  9, 11,  6,  8,  6,  4, 10,  1, 11,  8, 10,  5, 10,  6,  6,  6,
        2,  9,  6,  8, 10, 11,  5,  1,  5,  6,  6,  6,  6,  3,  3,  4,  6,
        5,  3, 10, 11,  3,  6,  6, 10,  8,  6,  5,  5,  1,  8,  8, 11, 11,
       10, 11,  3,  8, 11,  4,  6, 10,  6,  3,  6,  3,  6,  8,  6,  6,  7,
        4,  8,  2,  6,  8,  8, 10, 10,  9,  8,  3,  2,  5,  3, 11, 10, 11,
        3,  6,  3,  2,  6,  2, 11, 10,  6,  4,  8,  8,  9, 11,  3, 11,  7,
        6,  8,  2,  9,  3,  3,  8,  6,  5,  3,  3,  5,  9, 11,  6,  8,  6,
        4,  5,  2,  8, 11,  4, 10,  2,  3,  8,  8,  6,  9,  5,  5, 11,  8,
        3,  6,  3, 10, 10,  8,  1,  3,  6,  6,  6,  2,  2,  8,  5,  9,  6,
       10,  5,  6,  2, 11,  8,  6,  5,  3,  8, 10,  9, 11, 10,  3,  9,  3,
       11,  5,  6,  5,  5, 10, 10, 10,  6, 10, 11,  6,  6, 10, 10,  9,  6,
        3,  9, 11,  6,  6,  5,  1,  6,  6,  1,  3,  8,  2,  5,  6,  8,  9,
       10,  6,  3,  6, 10,  3,  1, 10,  6, 11,  9,  4,  7,  3,  7,  1,  8,
        3,  1,  8,  5,  2,  6, 10,  6,  3, 11,  1,  5,  5,  3,  3,  5,  4,
        6, 11, 10,  6,  6,  6, 10,  6,  9,  8,  6,  8,  6,  9, 11,  6,  1,
        6, 11,  3,  2,  1,  3,  8,  7, 10, 10,  6,  6,  8, 10,  3,  5,  2,
       10,  6, 10,  8, 11,  8, 11,  3,  1,  6,  3,  1, 11, 11,  5,  6])
In [ ]:
#We have to convert the labels from digits to 0s and 1s like we did for y_train, y_val and y_test
#Use labelEncoder like how we transformed the y labels previously.

y_pred = enc.fit_transform(rounded_predictions)
y_pred
Out[ ]:
array([[0, 1, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 1],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]])
In [ ]:
# Create a multiclass confusion matrix.
# In order to create the confusion matrix, we need to use argmax because it does not accept on-hot-encoding entries.

from sklearn.metrics import classification_report, confusion_matrix                        #Re-wrote this for convenience

cm = confusion_matrix(
    y_test.argmax(axis=1), y_pred.argmax(axis=1))
cm
Out[ ]:
array([[ 2,  0,  0,  0,  1,  0, 32,  0,  0,  0,  0,  2],
       [ 0, 54,  1,  0,  0,  0,  0,  0,  3,  0,  1,  0],
       [ 0,  0, 33,  0,  1,  0,  0,  0,  1,  0,  2,  0],
       [ 0,  0,  0, 78,  0,  3,  0,  0,  6,  8,  0,  0],
       [ 0,  0,  1,  0, 13,  2, 13,  0,  2,  0,  0,  2],
       [ 0,  0,  0,  0,  0, 62, 12,  0,  0,  0,  1,  6],
       [ 1,  0,  0,  0,  0,  2, 88,  0,  0,  0,  0,  2],
       [ 0,  0,  0, 12,  0,  0,  0, 12,  0,  4,  0,  2],
       [ 0,  0,  0,  2,  0,  1,  0,  0, 69,  0,  0,  3],
       [ 0,  0,  0,  1,  0,  0,  0,  0,  3, 25,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  2, 77,  2],
       [ 0,  0,  0,  3,  0,  0,  1,  0,  2,  0,  0, 57]])

Confusion Matrix:

Columns = Prediction

Rows = Actual

As you can tell, the model performed decently on the unseen test data. You coud see high values running in the diagonal (from upper left to lower right), which suggests good predictions.

However, there are some seedlings that are heavily misclassified. 32 cases were misclassified as loose silky-bent when they actually were black-grass. Also, 12 cases of common chickweed were predicted, but they were in fact maize. Thirteen common wheat and 12 fat hen seedlings were misclassified as loose silky-bent.

From these results, the model is faulting on a lot of different species and predicting them to be loose silky-bent.

There are other single-digit residual misclassifications in the multiclass confusion matrix but they are not as significant as the ones just discussed.

In [ ]:
#Let's look at the classification report. It will report the precision, recall and F1-score for each label.

print("=== Classification Report ===")
print(classification_report(y_test, y_pred))
=== Classification Report ===
              precision    recall  f1-score   support

           0       0.67      0.05      0.10        37
           1       1.00      0.92      0.96        59
           2       0.94      0.89      0.92        37
           3       0.81      0.82      0.82        95
           4       0.87      0.39      0.54        33
           5       0.89      0.77      0.82        81
           6       0.60      0.95      0.74        93
           7       1.00      0.40      0.57        30
           8       0.80      0.92      0.86        75
           9       0.64      0.86      0.74        29
          10       0.95      0.95      0.95        81
          11       0.75      0.90      0.82        63

   micro avg       0.80      0.80      0.80       713
   macro avg       0.83      0.74      0.74       713
weighted avg       0.82      0.80      0.78       713
 samples avg       0.80      0.80      0.80       713

Although accuracy is a good metric to determine the strength of the model. Precision and recall are better determinants. All precision values were at least 60%, which is decent. The recall results were worse. 3 plant seedlings (0: black-grass, 4: common wheat and 7: maize) had very low results. Especially with black-grass, the CNN model was only able to predict 5% of all true black-grass seedlings, despite having 67% of its predictions correct for this plant. The CNN model had low recall values for common wheat and maize and showed 39-40% ability to accurately predict them of all the plant images of these 2 species (This was made clear from the confusion matrix, as well).

6.Visualize predictions for x_test[2], x_test[3], x_test[33], x_test[36], x_test[59]. (5 Marks)

In [ ]:
#Let's look at the probabilities for X_test[2] as an example of how the model makes its predictions.
#Refer back to previous cell where we generated the predictions for the previous question.
predictions[2]             
Out[ ]:
array([1.53806776e-11, 1.21036464e-04, 3.22582281e-08, 2.56375849e-01,
       8.84931950e-08, 8.94376353e-05, 4.48668924e-09, 6.94922745e-01,
       1.14030316e-02, 3.62095349e-02, 2.22468261e-05, 8.55911872e-04],
      dtype=float32)

You could clearly see that the highest probability is 6.94922745e-01, which is in the 8th position (which is #7 in Python because Python starts with 0). So, we would expect 7 to pop up if we were to use the argmax function to find the predictions.

In [ ]:
#This is the classification order for all the seedlings used from labelencoder previously.
list(le.classes_)
Out[ ]:
['Black-grass',
 'Charlock',
 'Cleavers',
 'Common Chickweed',
 'Common wheat',
 'Fat Hen',
 'Loose Silky-bent',
 'Maize',
 'Scentless Mayweed',
 'Shepherds Purse',
 'Small-flowered Cranesbill',
 'Sugar beet']
In [ ]:
#For just finding what seedling type each image is predicted to be, use argmax. 
#Please use the above order to classify each image. 
rounded_predictions = np.argmax(predictions, axis = -1)

print("X_test[2]:")
print(rounded_predictions[2])
print("")
print("X_test[3]")
print(rounded_predictions[3])
print("")
print("X_test[33]")
print(rounded_predictions[33])
print("")
print("X_test[36]")
print(rounded_predictions[36])
print("")
print("X_test[59]")
print(rounded_predictions[59])
X_test[2]:
7

X_test[3]
6

X_test[33]
10

X_test[36]
3

X_test[59]
6

X_test[2] is predicted to be: Maize

X_test[3] is predicted to be: Loose Silky-Bent

X_test[33] is predicted to be: Small Flowered-Cranesbill

X_test[36] is predicted to be: Common Chickweed

X_test[59] is predicted to be: Loose Silky-Bent

In [ ]:
#EXTRA: Let's also compare these predictions to the actual labels to see how many we got correctly.

print("Actual label for image 2")
print(y_test[2])
print("")
print("Actual label for image 3")
print(y_test[3])
print("")
print("Actual label for image 33")
print(y_test[33])
print("")
print("Actual label for image 36")
print(y_test[36])
print("")
print("Actual label for image 59")
print(y_test[59])
[0 0 0 0 0 0 0 1 0 0 0 0]
[0 0 0 0 0 0 1 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 1 0]
[0 0 0 1 0 0 0 0 0 0 0 0]
[1 0 0 0 0 0 0 0 0 0 0 0]

Correct labels: Image 2: Maize Image 3: Loose silky-bent Image 33: Small-flowered Cranesbill Image 36: Common chickweed Image 59: Black-grass

Of all 5 predictions, image 59 was wrongly predicted. So, 1/5 was incorrect, which is expected since the accuracy score of this CNN model is 80% for the test data.

In [ ]:
 
In [ ]:
 

EXTRA: I re-ran the same model and achieved these results. This is to see if the results are generalizable and similar to the ones achieved above.

In [ ]:
#Accuracy line graph from a second trial of the model ran above.

plt.plot(history.history['accuracy'], label='train')
plt.plot(history.history['val_accuracy'], label='test')
plt.legend()
plt.show()

Clearly, the validation line shows that it follows the general trend of the test line. The only difference is that on this second running of the model, there was still a detectable loss in the latter epochs for it to continue running through the 35th epoch.

Regular metrics for the same model (second time)

In [ ]:
scores = model.evaluate(x_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])
23/23 [==============================] - 3s 144ms/step - loss: 3.7945 - accuracy: 0.8289
Test loss: 3.7945263385772705
Test accuracy: 0.8288919925689697
In [ ]:
scores = model.evaluate(x_train, y_train, verbose=1)
print('Train loss:', scores[0])
print('Train accuracy:', scores[1])
104/104 [==============================] - 16s 149ms/step - loss: 3.6676 - accuracy: 0.8818
Train loss: 3.6676461696624756
Train accuracy: 0.8818045258522034
In [ ]:
scores = model.evaluate(x_val, y_val, verbose=1)
print('Validation loss:', scores[0])
print('Validation accuracy:', scores[1])
23/23 [==============================] - 3s 144ms/step - loss: 3.7453 - accuracy: 0.8624
Validation loss: 3.7453086376190186
Validation accuracy: 0.8623595237731934

You can see that the metric scores are very similar. to the original model that I worked on. Accuracy on unseen data (test data) for this second trial is 83% compared to the original trial (80%). This goes to show this model is generalizable, and can perhaps enter into production if an estimated 80-83% accuracy on testing is acceptable.

This second trial results have better performance in general. In terms of overfitting, the first trial shows no overall fitting as a whole.